﻿using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace Hsenl.Network {
    public sealed class KcpClient : Connector {
        private UdpSocket _connector = new();
        private KcpChannel _channel;
        private EndPoint _remoteEndPoint;
        private EndPoint _realRemoteEndPoint;

        private readonly HBuffer _recvBuffer = new();
        private readonly ConcurrentQueue<(byte[] data, int offset, int count, EndPoint remoteEndPoint)> _sendQueue = new();
        private HTask<int> _connectTask;
        private readonly ArrayPool<byte> _kcpArrayPool = ArrayPool<byte>.Create(2048, 50);

        public Configure Config { get; private set; }
        public Container Container { get; private set; }
        public override bool IsConnecting => !this._connectTask.IsNull;
        public override bool IsConnected => this._channel != null;

        public EndPoint RealRemoteEndPoint {
            get {
                if (this._realRemoteEndPoint == null)
                    return this._remoteEndPoint;
                else
                    return this._realRemoteEndPoint;
            }
        }

        private readonly object _locker = new();

        public KcpClient(Configure config) {
            this.Config = config ?? throw new ArgumentNullException(nameof(config));

            this.Container = new Container();
            this.Container.Let<KcpChannel>().AllowAutoInjection();
            this.Container.Let<KcpPacketReceiverSender>().As<IKcpHandler>().ShareInjection();
            this.Container.Let<KcpPacketReceiverSender>().As<IPacketReceiver>().ShareInjection();
            this.Container.Let<KcpPacketReceiverSender>().As<IPacketSender>().ShareInjection();
            this.Container.Let<KcpPacketReceiverSender>().As<IMessageReader>().ShareInjection();
            this.Container.Let<KcpPacketReceiverSender>().As<IMessageWriter>().ShareInjection();

            this.Init();
        }

        public KcpClient(Configure config, Container container) {
            this.Config = config ?? throw new ArgumentNullException(nameof(config));
            this.Container = container ?? throw new ArgumentNullException(nameof(container));
            this.Init();
        }

        private void Init() {
            this._remoteEndPoint = this.Config.GetRemoteIPEndPoint();
            if (this._remoteEndPoint == null)
                throw new Exception("Remote IPEndPoint Invalid!");

            var socket = new Socket(this._remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
            socket.ReceiveBufferSize = this.Config.RecvBufferSize;
            socket.SendBufferSize = this.Config.SendBufferSize;
            socket.Bind(new IPEndPoint(IPAddress.Any, 0));

#if NET45_OR_GREATER
            const int SIO_UDP_CONNRESET = -1744830452;
            socket.IOControl(SIO_UDP_CONNRESET, new byte[] { 0 }, null);
#else
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
                const int SIO_UDP_CONNRESET = -1744830452;
                socket.IOControl(SIO_UDP_CONNRESET, new byte[] { 0 }, null);
            }
#endif

            UdpIOCPReceiver receiver;
#if UNITY_5_3_OR_NEWER
            receiver = new UdpReceiver(socket, SocketBufferKind.Array);
#else
            receiver = new UdpIOCPReceiver(socket, SocketBufferKind.Memory);
#endif
            this._connector.AddReceiver(receiver);
#if UNITY_5_3_OR_NEWER
            receiver.RecvBufferGetterArray += len => {
                if (len > this._recvBuffer.Length)
                    this._recvBuffer.SetLength(len);

                var buffer = this._recvBuffer.GetBuffer();
                return (buffer, 0, buffer.Length);
            };
#else
            receiver.RecvBufferGetter += len => {
                if (len > this._recvBuffer.Length)
                    this._recvBuffer.SetLength(len);
            
                return this._recvBuffer.GetMemory(len);
            };
#endif
            receiver.OnRecvFromDataEvent += this.OnRecvFromData;

            UdpIOCPSender sender;

#if UNITY_5_3_OR_NEWER
            sender = new UdpSender(socket, SocketBufferKind.Array);
#else
            sender = new UdpIOCPSender(socket, SocketBufferKind.Memory);
#endif
            this._connector.AddSender(sender);

#if UNITY_5_3_OR_NEWER
            sender.SendBufferGetterArray += len => {
                if (this._sendQueue.TryDequeue(out var tuple)) {
                    return (tuple.data, tuple.offset, tuple.count, tuple.remoteEndPoint);
                }

                if (!this.IsConnected)
                    return default;

                var bytes = this._channel.GetSendBuffer(len, out var offset, out var count);
                return (bytes, offset, count, this._channel.RemoteEndPoint);
            };
#else
            sender.SendBufferGetter += len => {
                if (this._sendQueue.TryDequeue(out var tuple)) {
                    return (tuple.data.AsMemory(tuple.offset, tuple.count), tuple.remoteEndPoint);
                }

                if (!this.IsConnected)
                    return default;

                var memory = this._channel.GetSendBuffer(len);
                return (memory, this._channel.RemoteEndPoint);
            };
#endif
            sender.OnSendToDataEvent += this.OnSendToData;

            this._connector.ReceiveFromAsync();

            new Thread(() => { _ = this.KcpDriver(); }) {
                IsBackground = true,
                Name = "KcpClient Worker",
            }.Start();
        }

        /// <returns>0: 成功, 1: 正在连接, 2: 连接失败, -1: 已经连接了</returns>
        public override async HTask<int> ConnectAsync() {
            this.CheckDisposedException();
            if (this.IsConnected)
                return -1;

            if (this.IsConnecting)
                return 1;

            this.SendSYNTo(this._remoteEndPoint);

            this._connectTask = HTask<int>.Create();
            Timeout();
            var ret = await this._connectTask;
            await HTask.SwitchToMainThread();

            return ret;

            async void Timeout() {
                await Task.Delay(8000);
                this._connectTask.SetResult(2);
            }
        }

        private void OnRecvFromData(EndPoint remoteEndPoint, Memory<byte> data) {
            // 根据发来的数据, 判断是要连接, 还是发送消息
            var span = data.Span;
            var head = span[0];
            var remoteConv = BitConverter.ToUInt32(span.Slice(1));
            this.OnChannelRecvData(remoteConv, data);
            switch (head) {
                case KcpProtocalType.SYN: {
                    break;
                }

                case KcpProtocalType.ACK: {
                    if (this._channel == null) {
                        var port = BitConverter.ToUInt16(span.Slice(5));
                        if (port != 0) {
                            this._realRemoteEndPoint = new IPEndPoint(((IPEndPoint)remoteEndPoint).Address, port);
                        }

                        this._connectTask.SetResult(0);

                        var channel = this.CreateChannel();
                        if (channel == null)
                            throw new Exception("Create Channel Fail!");

                        channel.Service = this;
                        channel.UserToken = this._connector;
                        channel.OnMessageReadedEvent += this.OnMessageReaded;
                        channel.OnMessageWritedEvent += this.OnMessageWrited;
                        channel.OnErrorEvent += this.OnChannelError;
                        channel.Init(remoteConv, this.RealRemoteEndPoint);
                        this._channel = channel;

                        Log.Info($"客户端'{this._connector.Receiver.Socket.LocalEndPoint}'连接到了服务器: {this.RealRemoteEndPoint}");

                        foreach (var plug in this.ForeachPlugs<IOnChannelStarted>()) {
                            try {
                                plug.Handle(this._channel.ChannelId);
                            }
                            catch (Exception e) {
                                Log.Error(e);
                            }
                        }
                    }

                    break;
                }

                case KcpProtocalType.FIN: {
                    lock (this._locker) {
                        var error = BitConverter.ToInt32(span.Slice(5));
                        this.OnDisconnect(error);
                    }

                    break;
                }

                case KcpProtocalType.MSG: {
                    var channel = this._channel;
                    if (channel == null) {
                        this.SendFINTo(remoteConv, ErrorCode.ConnectionAborted, remoteEndPoint);
                        break;
                    }

                    channel.Read(data);
                    break;
                }
            }
        }

        private void OnSendToData(EndPoint remoteEndPoint, Memory<byte> data) {
            var remoteConv = 0u;
            if (data.Length != 1)
                remoteConv = BitConverter.ToUInt32(data.Slice(1).Span);
            this.OnChannelSendData(remoteConv, data);
        }

        public override Channel GetChannel(long channelId) {
            return this._channel;
        }

        public KcpChannel GetChannel() {
            return this._channel;
        }

        public override ICollection<long> GetChannelIds() {
            return new Collection<long>() { this._channel.ChannelId };
        }

        private KcpChannel CreateChannel() {
            this.Container.StartStage();
            var channel = this.Container.Resolve<KcpChannel>();
            this.Container.EndStage();
            return channel;
        }

        protected override void OnChannelError(long channelId, int errorCode) {
            try {
                base.OnChannelError(channelId, errorCode);
            }
            finally {
                if (ErrorCode.fatalSocketErrors.Contains(errorCode) || errorCode > ErrorCode.SocketErrorWatershed) {
                    this.Disconnect(errorCode);
                }
            }
        }

        public override void Write(long channelId, byte[] data, int offset, int count) {
            if (!this.IsConnected) {
                return;
            }

            this._channel.Write(data, offset, count);
        }

        public override void Write(long channelId, Span<byte> data) {
            if (!this.IsConnected) {
                return;
            }

            this._channel.Write(data);
        }

        public override void StartSend(long channelId) {
            if (!this.IsConnected) {
                return;
            }

            this._connector.Sender.SendToAsync();
        }

        public override void OnUpdate() {
            try {
                this._channel?.OnUpdate();
            }
            catch (Exception e) {
                Log.Error(e);
            }
        }

        private async hvoid KcpDriver() {
#if NETCOREAPP3_0_OR_GREATER
            var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(16));
            while (!this.IsDisposed && await timer.WaitForNextTickAsync()) {
#else
            while (!this.IsDisposed) {
#endif
                var currentTime = TimeInfo.CurrentUtcMilliseconds;
                this._channel?.KcpUpdate(currentTime);
                Thread.Sleep(16);
            }
        }

        private byte[] GetSendToBytes(int len) {
            return new byte[len];
        }

        private void SendSYNTo(EndPoint remoteEndPoint) {
            var bytes = this.GetSendToBytes(1);
            bytes[0] = KcpProtocalType.SYN;
            this.WriteToSendQueue(bytes, 0, 1, remoteEndPoint);
        }

        private void SendFINTo(uint localConv, int errorId, EndPoint remoteEndPoint) {
            var bytes = this.GetSendToBytes(9);
            bytes[0] = KcpProtocalType.FIN;
            bytes.WriteTo(1, localConv);
            bytes.WriteTo(5, errorId);
            this.WriteToSendQueue(bytes, 0, 9, remoteEndPoint);
        }

        public void SendPingTo(uint localConv, EndPoint remoteEndPoint) {
            var bytes = this.GetSendToBytes(9);
            bytes[0] = KcpProtocalType.Ping;
            bytes.WriteTo(1, localConv);
            bytes.WriteTo(5, 10000000);
            this.WriteToSendQueue(bytes, 0, 9, remoteEndPoint);
        }

        private void WriteToSendQueue(byte[] data, int offset, int count, EndPoint remoteEndPoint) {
            this._sendQueue.Enqueue((data, offset, count, remoteEndPoint));
            this._connector.Sender.SendToAsync();
        }

        public override void Disconnect(int error) {
            // 当channel发生错误导致被disconnect的时候, 是通过多线程调用的, 所以加锁
            lock (this._locker) {
                if (this._channel != null) {
                    this.SendFINTo((uint)this._channel.ChannelId, error, this._channel.RemoteEndPoint);
                    this.OnDisconnect(error);
                }
            }
        }

        private void OnDisconnect(int error) {
            if (this._channel != null) {
                var channleId = this._channel.ChannelId;
                Log.Info($"客户端{this._connector.Receiver.Socket.LocalEndPoint}断开连接");
                this._channel.Dispose();
                this._channel = null;

                foreach (var plug in this.ForeachPlugs<IOnChannelDisconnected>()) {
                    try {
                        plug.Handle(channleId, error);
                    }
                    catch (Exception e) {
                        Log.Error(e);
                    }
                }
            }
        }

        public override void Dispose() {
            if (this.IsDisposed)
                return;

            base.Dispose();

            this.Disconnect(ErrorCode.ConnectionAborted);

            this.Config.Dispose();
            this.Config = null;
            this.Container.Dispose();
            this.Container = null;

            this._connector?.Dispose();
            this._connector = null;
            this._channel?.Dispose();
            this._channel = null;
            this._remoteEndPoint = null;
            this._realRemoteEndPoint = null;
            this._recvBuffer.Dispose();
            this._sendQueue.Clear();
            this._connectTask.Abort();
        }

        public class Configure {
            public string RemoteIPHost { get; set; }
            public int Port { get; set; }
            public int RecvBufferSize { get; set; }
            public int SendBufferSize { get; set; }

            public IPEndPoint GetRemoteIPEndPoint() {
                if (IPAddress.TryParse(this.RemoteIPHost, out var address)) {
                    var endPoint = new IPEndPoint(address, this.Port);
                    return endPoint;
                }

                return null;
            }

            public void Reset() {
                this.RemoteIPHost = null;
                this.Port = 0;
                this.RecvBufferSize = 0;
                this.SendBufferSize = 0;
            }

            public void Dispose() {
                this.Reset();
            }
        }
    }
}